iT邦幫忙

2024 iThome 鐵人賽

DAY 22
1
JavaScript

30天的 JavaScript 設計模式之旅系列 第 22

[Day 22] 程式碼拆分(Code Splitting)與動態匯入(Dynamic Import) (1)

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20241006/20168201xdNfJb5lic.png

今明兩天要介紹的是 React 中的程式碼拆分(Code Splitting)與動態匯入(Dynamic Import),會先從一般的靜態匯入開始介紹,說明靜態匯入可能產生的問題,再接續介紹程式碼拆分與動態匯入。雖然之前在 Module 模式已經有介紹過靜態與動態匯入,不過那時是著重於純 JavaScript 的匯入,這兩天會著重介紹在 React 中的程式碼拆分與動態匯入。

React 中的靜態匯入

Module 模式的文章內曾提過,使用 import 關鍵字可匯入另一個模組 export 匯出的模組,在 React 中我們通常會先在一個檔案內定義一個元件,並在要使用的地方匯入該元件做使用,例如以下:

// Text.js
// 在 Text.js 檔案定義 Text 元件並匯出
const Text = ({ color = 'black', children }) => {
    return <p style={{ color }}>{children}</p>;
};
export default Text


// App.js
// 在 App.js 檔案匯入 Text 元件做使用
import Text from './Text.js' // 當 JS 引擎執行到匯入模組的這行程式碼時,就會執行匯入
const App = () => {
    return (
        <>
            <Text color="blue">Hello!🌼</Text>
            <Text color="green">HiHi🙌</Text>
        </>
    );
}
export default App;

當我們使用 import Text from './Text.js' 的時候,就屬於靜態匯入,在預設情況下,靜態匯入的所有模組都會被加到初始 bundle 中。在打包 React 應用程式程式碼時,打包工具(如 webpack)會將Text 元件的程式碼和 App 元件的程式碼一起打包到一個初始 bundle 內。
靜態匯入是開發 React 時最常見的匯入方式,優點包括:

  • 語法簡潔明確:直接指名來源路徑和需要的特定模組,程式碼結構能清晰看出所需的模組
  • 開發時可獲得即時回饋:在開發階段時,若撰寫錯誤的匯入路徑或名稱就會被編譯器立即提醒,有助於快速除錯,提高程式碼穩定性

靜態匯入可能有什麼問題?

靜態匯入看似運作順利,但可能有些潛在問題。隨著應用程式越來越大,我們打包的 bundle 檔案就會變得越來越肥大,就會影響應用程式的載入時間。
假設我們的 App.js 匯入了多種元件,這些元件都會被一起打包到最終的 bundle 檔案內,且每個子元件都可能有自己的樣式和功能。如果這些元件的程式碼量很大,或者它們包含大量的圖片或其他資源,最後打包的 bundle 檔案大小就會太大,而導致應用程式載入時間過長,影響應用的使用者體驗。

// App.js
import Header from './Header';
import Footer from './Footer';
import Sidebar from './Sidebar';
import Content from './Content';
import Navbar from './Navbar';
import Modal from './Modal';
import ToolTip from './ToolTip';
import Dashboard from './Dashboard';
import Settings from './Settings';

const App = () => {
    return (
        <div>
            <Header />
            <Navbar />
            <div className="main-content">
                <Sidebar />
                <Content />
            </div>
            <Modal />
            <ToolTip />
            <Dashboard />
            <Settings />
            <Footer />
        </div>
    );
};

export default App;

為何會影響應用的使用者體驗?因為在 App.js 渲染畫面之前,必須先載入並剖析所有模組,完成所有步驟後使用者才能看到畫面、和應用程式互動。瀏覽器處理 JavaScript 和渲染畫面的完整步驟如下:

  1. Loading JavaScript(載入 JS):從伺服器下載 JavaScript 文件到瀏覽器。
  2. Parsing JavaScript(解析 JS):瀏覽器解析 JavaScript 代碼,將其轉換為抽象語法樹(Abstract Syntax Tree,AST)。這步驟是將 JavaScript 程式碼語法結構化的過程,以便進一步編譯和執行。
  3. Compiling JavaScript(編譯 JS):JavaScript 引擎將解析過的程式碼編譯為機器碼或中間碼,以便更快地執行。現代 JavaScript 引擎(如 V8 引擎)會在編譯的同時進行優化,以提高程式碼運行的效率。
  4. Executing JavaScript(執行 JS):執行編譯後的 JavaScript 程式碼,實際進行程式碼中的操作,比如控制 DOM、處理數據等。
  5. 渲染對應畫面:根據 JavaScript 程式碼的執行結果,瀏覽器更新 DOM 並渲染對應的畫面,將最終的畫面顯示給使用者。

示意圖如下:
https://ithelp.ithome.com.tw/upload/images/20241006/20168201UbJYASNttE.jpg
圖 1 瀏覽器處理 JavaScript 流程示意(資料來源:自行繪製)

當我們的 main.bundle.js(也就是打包的最終 bundle)容量太大,載入和解析時間就會更長,就需要等待更久的時間才能渲染 App.js 的畫面,使用者會看到更久的白屏畫面,而影響了使用者體驗。

Code Splitting

那要如何讓使用者更快看到畫面呢? 就要減少 bundle 大小來加快上述步驟執行的時間(載入、處理和執行時間),我們可以不要一次載入一個肥大的 bundle,而是將 bundle 拆分為多個,只在使用者互動、或頁面需要時才動態匯入模組,如此可減少初始 bundle 的大小。
示意圖如下,我們將原先單一一個巨大的 bundle 拆分為多個小 bundle,在需要時再載入小 bundle。
https://ithelp.ithome.com.tw/upload/images/20241006/20168201rY8SxTu2Zc.jpg
圖 2 拆分 bundle(資料來源:自行繪製)

減少 main.bundle.js 大小,可讓使用者更快看到初始畫面。Before 和 After 示意圖如下。
https://ithelp.ithome.com.tw/upload/images/20241006/20168201wZg3RO865u.jpg
圖 3 程式碼拆分 Before 與 After(資料來源:自行繪製)

而這種「將單一一個 bundle 拆分為多個小 bundle」的概念就稱為 Code Splitting,Code Splitting 就是為了解決單一 bundle 檔案過大的問題,將單一的 bundle 切分為數個小 bundle,可以平行載入、也可以在有需要時才載入特定 bundle,也可對不常變動的 bundle 快取。雖然透過 code splitting 沒辦法減少應用程式中整體程式碼檔案大小(只是將單一一個 bundle 拆分成多個小的,所有小 bundle 加起來,檔案大小也會跟單一 bundle 差不多),但可以避免使用者在一次載入內花太多時間和流量下載那些用不到的檔案。Code Splitting 主要是改善使用者第一次載入應用所需的時間,來達到載入效能的優化。

示意圖中有提及 FCP、LCP 和 TTI,可以看出 Code Splitting 後能降低這 3 個指標度量的時間,以下簡要介紹 Code Splitting 帶來的好處以及指標的定義:

  • 減少首次內容繪製(First Contentful Paint, FCP)時間
    • FCP:瀏覽器將使用者看得見的內容渲染到螢幕上的開始時間
  • 減少最大內容繪製(Largest Contentful Paint, LCP)度量的時間
    • LCP:可視區域中最大圖片、文字區塊或影片的顯示時間
  • 降低可互動度量的時間(Time to Interactive, TTI)
    • TTI:從頁面載入開始到頁面處於完全可互動狀態所花費的時間
    • 只有在載入並執行 bundle 後,UI 才變得可互動,因此若 bundle 要載入、執行太久,使用者就要等更久才能互動

(關於更多衡量網頁效能的指標,之後文章會再做介紹)

了解 Code Splitting 解決的問題與帶來的好處後,就要來看看如何實現 Code Splitting。Code Splitting 可分為兩種常見技巧,一是抽離第三方套件,二是動態匯入模組/元件。明天將會聚焦介紹動態匯入的方法,對抽離第三方套件的實作技巧有興趣的推薦閱讀這篇文章

Reference


上一篇
[Day 21] Hooks 模式
下一篇
[Day 23] 程式碼拆分(Code Splitting)與動態匯入(Dynamic Import) (2)
系列文
30天的 JavaScript 設計模式之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言